﻿namespace Lilium.CSharp.Expressions.Substitutions

open System.Runtime.CompilerServices

[<Extension>]
module Reflection =
    
    open System
    open System.Linq.Expressions

    open Lilium.CSharp.Expressions.Evaluators
    open Lilium.CSharp.Expressions.Patterns
    open Lilium.CSharp.Expressions.Substitutions.Utils

    type private PropertyOrFieldSubstitution () =
        inherit SubstitutionAttribute ()

        override self.GetExpression transformer meth args typeArgs =
            match args, typeArgs with
            | objExpr' :: memberNameExpr :: [], memberTypeExpr :: [] 
                ->  let memberName = Eval (transformer memberNameExpr) :?> string
                    let memberType = Eval (transformer memberTypeExpr) :?> Type
                    let objectType = typeof<obj>
                    let objExpr = match transformer objExpr' with 
                                  | Convert (expr, objectType)  -> expr
                                  | other                       -> other
                    let expression = Expression.PropertyOrField(objExpr, memberName)
                    if (memberType.IsAssignableFrom(expression.Type))
                        then expression :> Expression
                        else Expression.Convert(expression, memberType) :> Expression
            | _ -> failwith "Excepting 2 (two) arguments and 1 (one) type argument"

    [<Extension>]
    [<PropertyOrFieldSubstitution>]
    let PropertyOrField (obj : obj) (propertyOrFieldName : string) : 'b = substitutionCall ()